package com.wondertek.mam.tag.service;

import com.wondertek.mam.tag.config.Constant;
import com.wondertek.mam.tag.domain.ArticleBean;
import com.wondertek.mam.tag.domain.RespTag;
import com.wondertek.mam.tag.domain.TagDB;
import com.wondertek.mam.tag.domain.TagWord;
import com.wondertek.mam.tag.repository.TagRepository;
import com.wondertek.mam.tag.web.rest.util.RespBody;
import com.wondertek.mam.tag.web.rest.vm.ReqPostTag;
import com.wondertek.mam.tag.web.rest.vm.ReqPutTag;
import org.apdplat.word.recognition.StopWord;
import org.apdplat.word.segmentation.Segmentation;
import org.apdplat.word.segmentation.Word;
import org.apdplat.word.tagging.PartOfSpeechTagging;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * 标签服务类
 * Created by wangdongxu on 2017-09-05 下午 16:18:10.
 */
@Service
public class TagService {
    private static final Logger LOGGER = LoggerFactory.getLogger(TagService.class);

    @Autowired
    private Segmentation segmentation;
    @Autowired
    private TagWordProcessor tagWordProcessor;
    @Autowired
    private TagRepository tagRepository;

    /**
     * 获取文章的关键词
     * @param article 文章
     * @return 关键词
     */
    public RespBody getArticleTag(ArticleBean article) {
        LOGGER.info("开始文章标题统计");
        Map<Word, AtomicInteger> titleStatisticsMap = new ConcurrentHashMap<>();
        segCount(article.getTitle(),titleStatisticsMap);//标题分词统计
        LOGGER.info("开始文章正文统计");
         Map<Word, AtomicInteger> textStatisticsMap = new ConcurrentHashMap<>();
        segCount(article.getText(),textStatisticsMap);//正文分词统计

        //标题过滤词性无效的词语
        Set<Word> titleWords = titleStatisticsMap.keySet();
        PartOfSpeechTagging.process(new ArrayList<>(titleWords));
        titleWords = titleWords.parallelStream()
            .filter(word -> !Constant.EXCLUDE_PART_OF_SPEECH.contains(word.getPartOfSpeech().getPos()))
            .collect(Collectors.toSet());

        //正文过滤词频为1的词语
/*        textStatisticsMap = textStatisticsMap.entrySet().parallelStream()
            .filter(stringAtomicIntegerEntry -> stringAtomicIntegerEntry.getValue().get()>=1)
            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));*/
        //正文过滤词性无效的词语
        Set<Word> textWords;
        textWords = textStatisticsMap.keySet();
        PartOfSpeechTagging.process(new ArrayList<>(textWords));
        textWords = textWords.parallelStream()
            .filter(word -> !Constant.EXCLUDE_PART_OF_SPEECH.contains(word.getPartOfSpeech().getPos()))
            .collect(Collectors.toSet());
        String err = "";
        if (textWords.size()!=0){
            Collection<TagWord> textTagWords = tagWordProcessor.filterTag(textWords);
            if (textTagWords.size()!=0){
                Map<String, Float> resultMap = wordQScore(textTagWords, titleStatisticsMap, textStatisticsMap);
                List<String> collects = resultMap.entrySet().parallelStream()
                    .sorted((a, b) -> b.getValue().compareTo(a.getValue()))
                    .map(Map.Entry::getKey)
                    .collect(Collectors.toList());
                if (collects.size()>=Constant.TAG_SIZE) {
                    return RespBody.ok(getRespTag(collects.subList(0, Constant.TAG_SIZE)));
                }
                return RespBody.ok(getRespTag(collects));
            }
            String err1 = "标签库中没有正文中的相关词语:"+textWords.stream()
                .map(Word::getText)
                .collect(Collectors.toList()).toString();
            err +=err1;
            LOGGER.info(err1);
        }else {
            err += "正文没有分出有效的词汇";
            LOGGER.info("正文没有分出有效的词汇");
        }
        if(titleWords.size()!=0) {
            Collection<TagWord> tileTagWords = tagWordProcessor.filterTag(titleWords);
            if (tileTagWords.size() != 0) {
                Map<String, Float> resultMap = wordQScore2(tileTagWords, titleStatisticsMap);
                List<String> collects = resultMap.entrySet().parallelStream()
                    .sorted((a, b) -> b.getValue().compareTo(a.getValue()))
                    .map(Map.Entry::getKey)
                    .collect(Collectors.toList());
                if (collects.size() >= Constant.TAG_SIZE) {
                    return RespBody.ok(getRespTag(collects.subList(0, Constant.TAG_SIZE)));
                }
                return RespBody.ok(getRespTag(collects));
            }
            String err2 = ",标签库中没有标题中的相关词语:"+titleWords.stream()
                .map(Word::getText)
                .collect(Collectors.toList())
                .toString();
            err += err2;
            LOGGER.info(err2);
        }else {
            err += ",标题没有分出有效的词汇";
            LOGGER.info(",标题没有分出有效的词汇");
        }
        return RespBody.build("001",err);
    }

    /**
     * 获取标签
     * @param tag 标签
     * @param score 等级
     * @param hot 热词
     * @param createTimeStart 创建开始时间
     * @param createTimeEnd 创建结束时间
     * @param updateTimeStart 更新开始时间
     * @param updateTimeEnd 更新结束时间
     * @param protectedTimeStart 受保护开始时间
     * @param protectedTimeEnd 受保护结束时间
     * @param pageable 分页
     */
    public Page<TagDB> getTags(String tag, Integer score, Boolean hot, LocalDateTime createTimeStart,
                        LocalDateTime createTimeEnd, LocalDateTime updateTimeStart,
                        LocalDateTime updateTimeEnd, LocalDate protectedTimeStart,
                        LocalDate protectedTimeEnd,Integer groupId, Pageable pageable){
        return tagRepository.findAll((root, criteriaQuery, criteriaBuilder) -> {
            Path<String> tagPath = root.get("tag");
            Path<Integer> scorePath = root.get("score");
            Path<Boolean> hotPath = root.get("hot");
            Path<LocalDateTime> createTimeStartPath = root.get("createTime");
            Path<LocalDateTime> createTimeEndPath = root.get("createTime");
            Path<LocalDateTime> updateTimeStartPath = root.get("updateTime");
            Path<LocalDateTime> updateTimeEndPath = root.get("updateTime");
            Path<LocalDate> protectedTimeStartPath = root.get("protectedTime");
            Path<LocalDate> protectedTimeEndPath = root.get("protectedTime");
            Path<Integer> groupIdPath = root.get("groupId");
            //Path<Boolean> groupBoolPath = root.get("groupBool");

            List<Predicate> ps = new ArrayList<>();
            if (tag!=null&&!tag.isEmpty()) {
                ps.add(criteriaBuilder.like(tagPath, "%" +tag+"%"));
            }
            if (score!=null) {
                ps.add(criteriaBuilder.equal(scorePath, score));
            }
            if (hot!=null) {
                ps.add(criteriaBuilder.equal(hotPath, hot));
            }
            if (createTimeStart!=null) {
                ps.add(criteriaBuilder.greaterThanOrEqualTo(createTimeStartPath, createTimeStart));
            }
            if (createTimeEnd!=null) {
                ps.add(criteriaBuilder.lessThanOrEqualTo(createTimeEndPath, createTimeEnd));
            }
            if (updateTimeStart!=null) {
                ps.add(criteriaBuilder.greaterThanOrEqualTo(updateTimeStartPath, updateTimeStart));
            }
            if (updateTimeEnd!=null) {
                ps.add(criteriaBuilder.lessThanOrEqualTo(updateTimeEndPath, updateTimeEnd));
            }
            if (protectedTimeStart!=null) {
                ps.add(criteriaBuilder.greaterThanOrEqualTo(protectedTimeStartPath, protectedTimeStart));
            }
            if (protectedTimeEnd!=null) {
                ps.add(criteriaBuilder.lessThanOrEqualTo(protectedTimeEndPath, protectedTimeEnd));
            }
            if (groupId==0){
                ps.add(criteriaBuilder.notEqual(groupIdPath, 0));
            }else {
                ps.add(criteriaBuilder.equal(groupIdPath, groupId));
            }
            //criteriaBuilder.isFalse(groupBoolPath);
            if(ps.size()!=0){
                Predicate[] pArrs= new Predicate[ps.size()];
                ps.toArray(pArrs);
                criteriaQuery.where(pArrs);
            }
            return null;
        },pageable);
    }

    /**
     * 修改
     * @param tags 修改列表
     */
    public void postTags(List<ReqPostTag> tags){
        for (ReqPostTag reqPostTag : tags) {
                TagDB one = tagRepository.findOne(reqPostTag.getId());
                if (one ==null) {
                    return;
                }
                one.setUpdateTime(LocalDateTime.now());
                if (reqPostTag.getProtectedTime()!=null) {
                    one.setProtectedTime(reqPostTag.getProtectedTime());
                }
                if (reqPostTag.getScore()!=null){
                    if (reqPostTag.getScore()==5){
                        one.setScore(reqPostTag.getScore());
                        one.setHot(true);
                    }else {
                        one.setScore(reqPostTag.getScore());
                    }
                }
                tagRepository.save(one);
            }
    }


    /**
     * 更新分数
     */
    public void updateScore(LocalDateTime start,LocalDateTime end,Integer score){
        List<TagDB> allByCreateTimeBetweenAndScoreNot = tagRepository.findAllByUpdateTimeBetweenAndScoreNotAndProtectedTimeBefore(start,end,score, LocalDate.now().plusDays(1L));
        allByCreateTimeBetweenAndScoreNot.parallelStream().forEach(tagDB -> tagDB.setScore(score));
        tagRepository.save(allByCreateTimeBetweenAndScoreNot);

    }
    public void updateScore(LocalDateTime end,Integer score){
        List<TagDB> allByCreateTimeBetweenAndScoreNot = tagRepository.findAllByUpdateTimeBeforeAndScoreNotAndProtectedTimeBefore(end,score,LocalDate.now().plusDays(1L));
        allByCreateTimeBetweenAndScoreNot.parallelStream().forEach(tagDB -> tagDB.setScore(score));
        tagRepository.save(allByCreateTimeBetweenAndScoreNot);

    }
    /**
     * 删除旧的热点标记
     * @param endTime 结束时间
     */
    public void delHotFlag(LocalDateTime endTime){
        List<TagDB> allByHotIs = tagRepository.findAllByHotAndUpdateTimeBeforeAndProtectedTimeBefore(true,endTime,LocalDate.now().plusDays(1L))
            .parallelStream()
            .map(tagDB -> {
                tagDB.setHot(false);
                tagDB.setScore(4);
                return tagDB;
            })
            .collect(Collectors.toList());
        tagRepository.save(allByHotIs);

    }
    /**
     * 正文匹配成功的算分
     * @param tagWords 词汇库中的词汇和热度
     * @param titleStatisticsMap 标题词汇和词频
     * @param textStatisticsMap 正文筛选后词汇和词频
     * @return 结果
     */
    private Map<String,Float> wordQScore(Collection<TagWord> tagWords, Map<Word, AtomicInteger> titleStatisticsMap, Map<Word, AtomicInteger> textStatisticsMap){
        Map<String,Float> result = new HashMap<>();
        Map<String,Float> wordWQ = new HashMap<>();
        float hotTotal = 0;
        float textTotal = 0;
        Word tempWord;
        for (TagWord tw:tagWords) {
            tempWord = new Word(tw.getWord());
            hotTotal+=tw.getScore();
            AtomicInteger atomicInteger = titleStatisticsMap.get(tempWord);
            Float everyWQS;
            if (atomicInteger!=null){
                everyWQS = textStatisticsMap.get(tempWord).intValue()*(1+ Constant.TITLE_RATE);
            }else {
                everyWQS = (float) textStatisticsMap.get(tempWord).intValue();
            }
            wordWQ.put(tw.getWord(),everyWQS);
            textTotal+=everyWQS;
        }
        LOGGER.debug("匹配词语总热度:"+hotTotal);
        LOGGER.debug("加权后匹配词语总词频:"+hotTotal);

        for (TagWord tw:tagWords) {
            result.put(tw.getWord(),((tw.getScore()/hotTotal)* Constant.HOT_WQ_RATE+
                    (wordWQ.get(tw.getWord())/textTotal)* (1-Constant.HOT_WQ_RATE)));
        }
        LOGGER.debug("算分结果:"+result);
        return result;
    }

    /**
     * 正文匹配失败，标题成功的算分
     * @param tagWords 标签词语集合
     * @param titleStatisticsMap 标题词语词频集合
     * @return 结果
     */
    private Map<String,Float> wordQScore2(Collection<TagWord> tagWords, Map<Word, AtomicInteger> titleStatisticsMap){
        Map<String,Float> result = new HashMap<>();
        Map<String,Integer> wordWQ = new HashMap<>();//结果词汇词频得分集合
        int hotTotal = 0;
        int textTotal = 0;
        Word tempWord;
        //结果词汇词频得分算分
        for (TagWord tw:tagWords) {
            tempWord = new Word(tw.getWord());
            hotTotal+=tw.getScore();
            Integer everyWQS = titleStatisticsMap.get(tempWord).intValue();
            wordWQ.put(tw.getWord(),everyWQS);
            textTotal+=everyWQS;
        }
        LOGGER.debug("匹配词语总热度:"+hotTotal);
        LOGGER.debug("加权后匹配词语总词频:"+hotTotal);
        for (TagWord tw:tagWords) {
            result.put(tw.getWord(),((tw.getScore()/hotTotal)* Constant.HOT_WQ_RATE+
                    (wordWQ.get(tw.getWord())/textTotal)*(1- Constant.HOT_WQ_RATE)));
        }
        LOGGER.debug("算分结果:"+result);
        return result;
    }

    /**
     * 分词统计
     * @param str 内容
     * @param statisticsMap 结果集
     */
    private void segCount(String str,Map<Word, AtomicInteger> statisticsMap){
        List<Word> seg = segmentation.seg(str);
        seg.parallelStream().forEach(word -> {
            //停用词过滤
            if(StopWord.is(word.getText())){
                return;
            }
            statistics(word, statisticsMap);
        });
    }

    /**
     * 统计词频
     * @param word 词
     * @param container 内存中保存词频的数据结构
     */
    private void statistics(Word word, Map<Word, AtomicInteger> container){
        container.putIfAbsent(word, new AtomicInteger());
        container.get(word).addAndGet(1);
    }

    /**
     * 区分热词和普通词
     * @param tags 词集合
     * @return RespTag
     */
    private RespTag getRespTag(List<String> tags){
        RespTag respTag = new RespTag();
        Map<Integer, AtomicInteger> groupIdsCount = new ConcurrentHashMap<>();
        Map<Integer, AtomicInteger> classIdsCount = new ConcurrentHashMap<>();

        tagRepository.findAllByTagIn(tags).forEach(tagDB -> {
            if (tagDB.getHot()){
                respTag.addHotTag(tagDB.getTag());
            }else {
                respTag.addGeneralTag(tagDB.getTag());
            }
            //未分组
            if (tagDB.getGroupId()==null||tagDB.getGroupId()==-1){
                return;
            }
            //本身为组名
            if (tagDB.getGroupId()==0){
                groupIdsCount.putIfAbsent(tagDB.getId(),new AtomicInteger());
                groupIdsCount.get(tagDB.getId()).addAndGet(1);
                return;
            }
            groupIdsCount.putIfAbsent(tagDB.getGroupId(),new AtomicInteger());
            groupIdsCount.get(tagDB.getGroupId()).addAndGet(1);
        });
        if (groupIdsCount.size()==0){
            return respTag;
        }
        if (groupIdsCount.size()!=0) {
            List<String> groupNames = tagRepository.findAll(groupIdsCount.keySet()).stream().map(TagDB::getTag).collect(Collectors.toList());
            respTag.setGroupTag(groupNames);
        }
        groupIdsCount.forEach((key, value) -> {
            Integer classId = tagRepository.findOne(key).getClassId();
            classIdsCount.putIfAbsent(classId, new AtomicInteger());
            classIdsCount.get(classId).addAndGet(value.get());
        });
        Integer classId = classIdsCount.entrySet().stream()
            .sorted((o1, o2) -> o2.getValue().get() - o1.getValue().get())
            .collect(Collectors.toList())
            .get(0)
            .getKey();
        respTag.setClassId(classId);
        return respTag;
    }

    public void delGroup(List<Integer> ids) {
        tagRepository.findAll(ids).forEach(tagDB -> {
            List<TagDB> allByGroupIdIn = tagRepository.findAllByGroupIdIn(Collections.singletonList(tagDB.getId()))
                .stream()
                .map(tagDB1 -> {
                    tagDB1.setGroupId(-1);
                    return tagDB1;
                })
                .collect(Collectors.toList());
            tagDB.setGroupId(-1);
            tagDB.setProtectedTime(LocalDate.now().plusDays(1L));
            tagDB.setClassId(null);
            tagRepository.save(tagDB);
            tagRepository.save(allByGroupIdIn);
        });
    }
}
